#define	_ITMC328_C_
#include "itmc328.h"
#include "string.h"
#include "delay.h"

static ITMC328_INSTANCE_TYPE g_Itmc328 = {
											S_UNINITED,
											{
												NULL,
												0
											}
										};

//-PUBLIC

ITMC328_ERROR_TYPE Itmc328_Init(void){
	ITMC328_ERROR_TYPE itmc328State;
	uint8_t retryN;
	bool reqAbort = FALSE;
	ErrorStatus hasReplied = ERROR;

	//------------------------------
	// Init hardware
	GPIO_Init(POWER_PORT, POWER_PIN, GPIO_Mode_Out_PP_Low_Fast);
	GPIO_SetBits(POWER_PORT, POWER_PIN);

	GPIO_ExternalPullUpConfig(GPIOC, /*GPIO_Pin_2 | */GPIO_Pin_3, ENABLE);
	CLK_PeripheralClockConfig(CLK_Peripheral_USART1, ENABLE);
	USART_DeInit(USART1);
	USART_Init(	USART1,
				(uint32_t)14400,
				USART_WordLength_8b,
				USART_StopBits_1,
				USART_Parity_No,
				(USART_Mode_TypeDef)(USART_Mode_Tx | USART_Mode_Rx));
	USART_Cmd(USART1, ENABLE);
	//------------------------------
#ifdef	SKIP_ON_ERROR
#undef	SKIP_ON_ERROR
#endif
#define	SKIP_ON_ERROR(r)	if((r) == ERROR){reqAbort = TRUE; goto skip_init;}

	retryN = MAX_CONNECTION_RETRY;
	while(retryN-- && hasReplied == ERROR){
		itmc328_cmd_sync();
		hasReplied = itmc328_waitCmd(CMD_ACK);
	}
	SKIP_ON_ERROR(hasReplied)
	SKIP_ON_ERROR(itmc328_waitCmd(CMD_SYNC))

skip_init:
	if(reqAbort == FALSE){
		itmc328_cmd_ack(CMD_SYNC, 0);
		itmc328State = ITMC328_OK;
		g_Itmc328.state = S_OK;
	}
	else{
		itmc328State = ITMC328_DISCONNECTED;
		g_Itmc328.state = S_DISCONNECTED;
	}
#undef	SKIP_ON_ERROR

	return itmc328State;
}

void Itmc328_DeInit(void){
	GPIO_ResetBits(POWER_PORT, POWER_PIN);
}

ITMC328_ERROR_TYPE Itmc328_TakeAPicture(ErrorStatus (*pfCopyByteCbk)(uint8_t)){
	ITMC328_ERROR_TYPE result;

	switch(g_Itmc328.state){
	case S_OK:
		if(pfCopyByteCbk){
#ifdef	WAIT_ACK
#undef	WAIT_ACK
#endif
#define	WAIT_ACK(cmd)	if(itmc328_waitAck(cmd) == ERROR){	\
							err = TRUE;							\
							goto exit_process;					\
						}

			bool err = FALSE;
			g_Itmc328.transfer.pfCopyByte = pfCopyByteCbk;

			itmc328_cmd_initial(RES_80x64);
			WAIT_ACK(CMD_INITIAL)

			itmc328_cmd_setPackageSize(PACKET_DATA_SIZE);
			WAIT_ACK(CMD_SET_PACKAGE_SIZE)

			itmc328_cmd_snapshot();
			WAIT_ACK(CMD_SNAPSHOT)

			itmc328_cmd_getPicture(SNAPSHOT);
			WAIT_ACK(CMD_GET_PICTURE)

			if(itmc328_get_data(NULL, &g_Itmc328.transfer.remainingDataSize) == ERROR)
				err = TRUE;

exit_process:
			if(err == TRUE)
				result = ITMC328_GENERIC_ERROR;
			else{
				g_Itmc328.state = S_WORKING;
				result = ITMC328_OK;
			}
#undef	WAIT_ACK
		}
		break;

	case S_WORKING:
		result = ITMC328_BUSY;
		break;

	default:
		result = ITMC328_DISCONNECTED;
		break;
	}

	return result;
}

ITMC328_ERROR_TYPE Itmc328_GetImageData(uint16_t *lenByte){
	ITMC328_ERROR_TYPE result = ITMC328_GENERIC_ERROR;

	if(g_Itmc328.state == S_WORKING){
		bool aborted = FALSE;
		uint16_t currID = 0;
		*lenByte = 0;

		while(g_Itmc328.transfer.remainingDataSize && aborted == FALSE){
			uint16_t currPktSize;

			itmc328_cmd_ack(CMD_GET_DATA, currID);
			if(itmc328_get_dataPkt(&currPktSize) == SUCCESS){
				g_Itmc328.transfer.remainingDataSize -= currPktSize;
				*lenByte += currPktSize;
			}
			else{
				aborted = TRUE;
			}
			currID++;
			delay_ms(100);	//empiricamente
		}

		if(aborted == FALSE){
			itmc328_cmd_ack(CMD_GET_DATA, LAST_DATA_PACKET_ID);
			g_Itmc328.state = S_OK;

			result = ITMC328_OK;
		}
	}

	return result;
}


//-PRIVATE
static ErrorStatus itmc328_cmd_initial(ITMC328_JPEG_RES resolution){
	g_Itmc328.txPkt.cmd = CMD_INITIAL;
	g_Itmc328.txPkt.par1 = 0x00;
	g_Itmc328.txPkt.par2 = 0x07;
	g_Itmc328.txPkt.par3 = 0x07;
	g_Itmc328.txPkt.par4 = resolution;

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

static ErrorStatus itmc328_cmd_getPicture(ITMC328_PIC_TYPE type){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_GET_PICTURE;
	g_Itmc328.txPkt.par1 = type;

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

static ErrorStatus itmc328_cmd_snapshot(void){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_SNAPSHOT;

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

static ErrorStatus itmc328_cmd_setPackageSize(uint16_t pkgSize){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_SET_PACKAGE_SIZE;
	g_Itmc328.txPkt.par1 = 0x08;
	g_Itmc328.txPkt.par2 = (uint8_t)(pkgSize & 0x00FF);
	g_Itmc328.txPkt.par3 = (uint8_t)((pkgSize >> 8) & 0x00FF);

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

static ErrorStatus itmc328_cmd_setBaudrate(ITMC328_BAUD_RATE baudRate){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_SET_BAUDRATE;
	g_Itmc328.txPkt.par1 = (uint8_t)(baudRate & 0x00FF);
	g_Itmc328.txPkt.par2 = (uint8_t)((baudRate >> 8) & 0x00FF);

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

static ErrorStatus itmc328_cmd_reset(ITMC328_RESET_TYPE type){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_RESET;
	g_Itmc328.txPkt.par1 = (uint8_t)type;

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

static ErrorStatus itmc328_cmd_powerOff(void){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_POWER_OFF;

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

//V
static ErrorStatus itmc328_get_data(ITMC328_PIC_TYPE *picType, uint32_t *len){
	ErrorStatus hasReceived = FALSE;

	if(sizeof(g_Itmc328.rxPkt.b) == itmc328_receive(g_Itmc328.rxPkt.b, sizeof(g_Itmc328.rxPkt.b), TRUE)){
		if(g_Itmc328.rxPkt.cmd == CMD_DATA){
			if(picType)
				*picType = (ITMC328_PIC_TYPE)g_Itmc328.rxPkt.par1;
			if(len)
				*len = (((uint32_t)g_Itmc328.rxPkt.par4) << 16) | (((uint32_t)g_Itmc328.rxPkt.par3) << 8) | ((uint32_t)g_Itmc328.rxPkt.par2);	// TODO: VERIFICARE

			hasReceived = TRUE;
		}
	}

	return hasReceived;
}

//V
static ErrorStatus itmc328_get_dataPkt(uint16_t *pktSize){
	ErrorStatus ok = FALSE;
	ITMC328_DATA_PACKET dataPkt;
	
	memset(&dataPkt, 0, sizeof(dataPkt));

	if(sizeof(dataPkt.id) == itmc328_receive((uint8_t *)&dataPkt.id, sizeof(dataPkt.id), TRUE)){
		dataPkt.id = htons(dataPkt.id);
		if(sizeof(dataPkt.dataSize) == itmc328_receive((uint8_t *)&dataPkt.dataSize, sizeof(dataPkt.dataSize), TRUE)){
			dataPkt.dataSize = htons(dataPkt.dataSize);
			if(dataPkt.dataSize <= g_Itmc328.transfer.remainingDataSize){
				if(g_Itmc328.transfer.pfCopyByte){
					uint8_t ch = 0;
					uint16_t verifyCode = dataPkt.dataSize + dataPkt.id;
					uint16_t cntByte;

					for(cntByte = 0; cntByte < dataPkt.dataSize; cntByte++){
						itmc328_getch(&ch);
						g_Itmc328.transfer.pfCopyByte(ch);
						verifyCode += ch;
					}
					itmc328_receive((uint8_t *)&dataPkt.verifyCode, sizeof(dataPkt.verifyCode), TRUE);
					dataPkt.verifyCode = htons(dataPkt.verifyCode);
						
					if(pktSize)
						*pktSize = dataPkt.dataSize;

					if(dataPkt.verifyCode == (verifyCode & 0x00FF))
						ok = TRUE;
					else
						ok = FALSE;
				}
			}
		}
	}

	return ok;
}

//V
static ErrorStatus itmc328_cmd_sync(void){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_SYNC;

	return itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

//V
static void itmc328_cmd_ack(ITMC328_CMD_ID cmd, uint16_t pkgId){
	memset(g_Itmc328.txPkt.b, 0, sizeof(g_Itmc328.txPkt));
	g_Itmc328.txPkt.cmd = CMD_ACK;
	if(cmd == CMD_GET_DATA){
		g_Itmc328.txPkt.par1 = 0x00;
		g_Itmc328.txPkt.par3 = (uint8_t)(pkgId & 0x00FF);
		g_Itmc328.txPkt.par4 = (uint8_t)((pkgId>>8) & 0x00FF);
	}
	else{
		g_Itmc328.txPkt.par1 = (uint8_t)cmd;
	}

	itmc328_send(g_Itmc328.txPkt.b, sizeof(g_Itmc328.txPkt));
}

//V
static void itmc328_get_nack(const uint8_t *parBuffer, ITMC328_NAK_ERROR_TYPE *error){
	*error = (ITMC328_NAK_ERROR_TYPE)parBuffer[1];
}

//V
static ErrorStatus itmc328_waitCmd(ITMC328_CMD_ID cmd){
	ErrorStatus hasReceivedCmd = ERROR;
	uint8_t retry = 10;	// si evita di usare un timer per il timeout

	while(retry && hasReceivedCmd == ERROR){
		ITMC328_CMD_ID rxCmd;
		if(SUCCESS == itmc328_receiveCmd(&rxCmd)){
			if(cmd == rxCmd)
				hasReceivedCmd = SUCCESS;
		}

		retry--;
	}

	return hasReceivedCmd;
}

//V
static ErrorStatus itmc328_waitAck(ITMC328_CMD_ID rxAckCmd){
	ErrorStatus hasReceived = ERROR;

	if(itmc328_receive(g_Itmc328.rxPkt.b, sizeof(g_Itmc328.rxPkt), TRUE) == sizeof(g_Itmc328.rxPkt.b)){
		if((CMD_ACK == g_Itmc328.rxPkt.cmd) && (((uint8_t)rxAckCmd) == g_Itmc328.rxPkt.par1)){
			hasReceived = SUCCESS;
		}
	}

	return hasReceived;
}

//V
static ErrorStatus itmc328_receiveCmd(ITMC328_CMD_ID *pRxCmd){
	ErrorStatus hasReceived;

	if(sizeof(g_Itmc328.rxPkt) == itmc328_receive(g_Itmc328.rxPkt.b, sizeof(g_Itmc328.rxPkt), FALSE)){
		*pRxCmd = g_Itmc328.rxPkt.cmd;
		hasReceived = SUCCESS;
	}

	return hasReceived;
}

//V
static uint16_t itmc328_receive(uint8_t *rxBuffer, uint16_t maxLen, bool wait){
	uint16_t nCh = 0;
	uint8_t retry = 1;

	if(wait)
		retry = 50;

	while(retry-- && !nCh){
		while(nCh < maxLen && itmc328_getch(rxBuffer) == SUCCESS){
			rxBuffer++;
			nCh++;
		}
	}

	return nCh;
}

uint8_t dbgRecv[64];
uint8_t iDbgRecv = 0;
static ErrorStatus itmc328_getch(uint8_t *c){
	ErrorStatus recv = SUCCESS;
	FlagStatus rxFlag;
	uint16_t retry = 200;	// si evita di usare un timer per il timeout

	do{
		rxFlag = USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
		retry--;
	}while((rxFlag == RESET) && retry);

	if(rxFlag == SET){
		*c = (uint8_t)USART_ReceiveData8(USART1);
		dbgRecv[iDbgRecv++] = *c;
		if(iDbgRecv >= countof(dbgRecv))
			iDbgRecv = 0;
	}
	else
		recv = ERROR;

	return recv;
}

//V
static ErrorStatus itmc328_send(const uint8_t *rxBuffer, uint8_t len){
	ErrorStatus hasSent = SUCCESS;

	while(len && hasSent == SUCCESS){
		hasSent = itmc328_putch(*rxBuffer++);
		len--;
	}

	return hasSent;
}


static ErrorStatus itmc328_putch(uint8_t c){
	ErrorStatus hasSent = SUCCESS;
	uint8_t timeout = 100;	//evita di usare un timer per il timeout

	while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET && --timeout);

	if(timeout)
		USART_SendData8(USART1, c);
	else
		hasSent = ERROR;

	return hasSent;
}
